﻿using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

namespace CommonEngine.Worker
{
    public interface IRecycleBin
    {
        bool HasNotificationException { get; }
    }

    public interface IChecking : IRecycleBin
    {
        Action<dynamic, CancellationToken> ProblemDataConsumingAction { get; set; }
        int Count { get; }
        void Collect();
        void Cancel();
        dynamic GetResult();
    }

    public interface IRecycleBin<TResult> : IRecycleBin
    {
        IChecking Checking { get; }
        void SetResult(TResult result);
        void SetResult(IProducerConsumerCollection<TResult> result);
    }

    public class RecycleBin<TResult> : IRecycleBin<TResult>, IChecking, IRecycleBin
    {
        private object _lockObj = new object();
        private ConcurrentQueue<TResult> _queue = new ConcurrentQueue<TResult>();
        private CancellationTokenSource _cancellationTokenSource;
        private int _bottomLine = 100;
        private Action<dynamic, CancellationToken> _ProblemDataConsumingAction;
        public Action<dynamic, CancellationToken> ProblemDataConsumingAction
        {
            get
            {
                lock (_lockObj)
                {
                    return _ProblemDataConsumingAction;
                }
            }
            set
            {
                lock (_lockObj)
                {
                    _ProblemDataConsumingAction = value;
                }
            }
        }

        private bool _hasNotification = false;
        public bool HasNotificationException
        {
            get { return this._hasNotification; }
        }

        public RecycleBin(int BottomLine = 100)
        {
            this._bottomLine = BottomLine;
        }

        public void SetResult(TResult result)
        {
            if (result != null)
                this._queue.Enqueue(result);

            if (this._queue.Count > this._bottomLine)
                this._hasNotification = true;
            else
                this._hasNotification = false;
        }

        public void SetResult(IProducerConsumerCollection<TResult> result)
        {
            if (result != null)
                foreach (var v in result)
                    this._queue.Enqueue(v);

            if (this._queue.Count > this._bottomLine)
                this._hasNotification = true;
            else
                this._hasNotification = false;
        }

        public IChecking Checking
        {
            get { return this; }
        }

        public int Count
        {
            get
            {
                return this._queue.Count;
            }
        }

        public void Collect()
        {
            this._cancellationTokenSource = new CancellationTokenSource();
            var token = this._cancellationTokenSource.Token;
            Task.Run(() => TaskProcessor(token));
        }

        private async Task TaskProcessor(CancellationToken token)
        {
            TResult workItem;
            await GetRandomDelay();
            do
            {
                Thread.Sleep(20);
                if (this._queue.TryDequeue(out workItem))
                {
                    try
                    {
                        if (this._ProblemDataConsumingAction != null)
                            this._ProblemDataConsumingAction(workItem, token);

                        if (!token.CanBeCanceled)
                            break;
                    }
                    catch
                    {
                        //---log---
                    }
                }
                await GetRandomDelay();
            }
            while (!token.IsCancellationRequested);
        }
        private Task GetRandomDelay()
        {
            int delay = new Random(DateTime.Now.Millisecond).Next(1500);
            return Task.Delay(delay);
        }

        public void Cancel()
        {
            if (!this._cancellationTokenSource.IsCancellationRequested)
                this._cancellationTokenSource.Cancel(false);
        }

        public dynamic GetResult()
        {
            TResult en = default(TResult);
            if (this._queue.TryDequeue(out en))
                return en;
            else
                return default(TResult);
        }
    }
}
